package com.ea.admin.permission;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ea.admin.mapper.*;
import com.ea.admin.pojo.Enum.Permission;
import com.ea.admin.pojo.Enum.RoleCode;
import com.ea.admin.pojo.po.AdminAccount;
import com.ea.admin.pojo.po.AdminFunctionPermission;
import com.ea.admin.pojo.po.AdminOperatingLog;
import com.ea.admin.utils.NowUserGetter;
import com.ea.common.auth.NowUser;
import com.ea.common.result.BizException;
import com.ea.common.result.ErrorCode;
import com.ea.common.utils.ParamUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
@Aspect
@Slf4j
public class PermissionLogAOP {
    @Resource
    NowUserGetter nowUserGetter;
    @Resource
    AdminAccountMapper adminAccountMapper;
    @Resource
    CompanyInfoMapper companyInfoMapper;
    @Resource
    AdminFunctionPermissionMapper adminFunctionPermissionMapper;
    @Resource
    AdminOperatingLogMapper adminOperatingLogMapper;
    @Resource
    DeptInfoMapper deptInfoMapper;
    @Resource
    PositionInfoMapper positionInfoMapper;
    @Resource
    UserInfoMapper userInfoMapper;
    @Resource
    MysqlBackupMapper mysqlBackupMapper;

    /**
     * 定义切点（切入位置）
     */
    @Pointcut("@annotation(com.ea.admin.pojo.Enum.Permission))")
    public void pointcut() {
    }

    /**
     * 下面是核心业务
     * 权限环绕通知
     * joinPoint连接点
     */
    @Around("pointcut()") //环绕通知
    public Object isAccessMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        //增强处理
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();//获取访问的目标方法签名
        Method targetMethod = methodSignature.getMethod(); //目标方法

        //如果该方法上没有权限注解，直接调用目标方法
        if (!targetMethod.isAnnotationPresent(Permission.class)) {
            return joinPoint.proceed();//放行，调用目标组件方法
        }
        // 获取自定义注解对象
        Permission permission = targetMethod.getAnnotation(Permission.class);
        // 根据对象获取注解值
        PermissionCode permissionCode = permission.permission_code();
        log.info("验证权限：{}", permissionCode.getCode());
        AdminFunctionPermission adminFunctionPermission;
        // 检查用户是否已登录
        NowUser nowUser = nowUserGetter.getNowUser();
        if (nowUser == null) {
            //用户未登录
            throw new BizException(ErrorCode.USER_LOGIN_ERROR_NO_LOGIN);
        }
        // 检查用户的账号状态
        AdminAccount adminAccount = adminAccountMapper.selectById(nowUser.getAccountId());
        if (adminAccount == null || adminAccount.getIsBlock() == 1) {
            //用户不存在或已被禁用
            throw new BizException(ErrorCode.USER_LOGIN_ERROR_USER_NOT_EXISTS);
        }

        LambdaQueryWrapper<AdminFunctionPermission> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(AdminFunctionPermission::getAccountId, nowUser.getAccountId());
        wrapper.eq(AdminFunctionPermission::getPermissionCode, permissionCode.getCode());
        adminFunctionPermission = adminFunctionPermissionMapper.selectOne(wrapper);
        // 权限比对
        if (adminFunctionPermission == null) {
            throw new BizException(ErrorCode.USER_FUNCTION_PERMISSION_ERROR);
        }

        // 权限验证通过，处理请求
        Object result = joinPoint.proceed();//放行
        // 根据处理结果，记录日志
        if (permission.content() != null && !permission.content().isEmpty()) {
            // 将操作内容记录到管理员日志中
            recordLog(permission, joinPoint, nowUser.getAccountId());
        }
        return result;
    }

    private void recordLog(Permission permission, ProceedingJoinPoint joinPoint, Long accountId) {
        //请求的参数
        Object[] args = joinPoint.getArgs();
        Map<String, Object> params = new HashMap<>();

        //使用反射，将参数转化为map
        for (Object arg : args) {
            //System.out.println(arg.toString());
            Class<?> clazz = arg.getClass();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                try {
                    parseIdToName(field, arg, params);
                    if (field.get(arg) != null) {
                        params.put(field.getName(), field.get(arg));
                    }
                } catch (Exception e) {
                    throw new BizException(ErrorCode.SYSTEM_ERROR);
                }
            }
        }


        //使用正则表达式获取content中需要替换的参数
        String originContent = permission.content();
        String content = originContent;
        if (Objects.equals(content, "在单位“{companyName}”下添加部门“{deptName}”，设置默认管理员为“{deptContactUsername}”，姓名为“{deptContactName}”，手机号码为“{deptContactPhoneNum}”")
                && params.get("deptContactAccountId") == null) {
            originContent = "在单位“{companyName}”下添加部门“{deptName}”";
            content = "在单位“{companyName}”下添加部门“{deptName}”";
        }
        if(Objects.equals(content,"添加单位“{companyName}”，设置默认管理员为“{companyContactUsername}”，姓名为“{companyContactName}”，手机号码为“{companyContactPhoneNum}”")&&
                params.get("companyContactName")==null){
            originContent = "添加单位“{companyName}”";
            content = "添加单位“{companyName}”";
        }
        //识别大括号
        Pattern pattern = Pattern.compile("\\{(.+?)}");
        Matcher matcher = pattern.matcher(originContent);
        while (matcher.find()) {
            String replace = matcher.group(0);
            String replaceParam = replace.replaceAll("[{}]", "");
            Object o = params.get(replaceParam);
            if (o == null) {
                throw new BizException(ErrorCode.SYSTEM_ERROR);
            }
            content = content.replace(replace, o.toString());
        }

        //将操作内容记录到管理员日志中
        AdminOperatingLog adminOperatingLog = new AdminOperatingLog();
        adminOperatingLog.setAccountId(accountId);
        adminOperatingLog.setContent(content);
        adminOperatingLog.setOperatingTime(LocalDateTime.now());
        adminOperatingLogMapper.insert(adminOperatingLog);
    }

    void parseIdToName(Field field, Object arg, Map<String, Object> params) throws IllegalAccessException {
        if (field == null) {
            throw new BizException(ErrorCode.SYSTEM_ERROR);
        }
        switch (field.getName()) {
            case "accountId":
                // 如果是accountId，查找出对应的username
                String username = adminAccountMapper.selectAdminAccountById(
                        ParamUtil.parseId((String) field.get(arg))
                ).getUsername();
                params.put("username", username);
                break;
            case "companyId":
                // 如果是companyId，查找出对应的companyName
                String companyName = companyInfoMapper.selectCompanyInfoById(
                        ParamUtil.parseId((String) field.get(arg))
                ).getCompanyName();
                params.put("companyName", companyName);
                break;
            case "deptId":
                // deptId，查找出对应的deptName
                String deptName = deptInfoMapper.selectDeptInfoById(
                        ParamUtil.parseId((String) field.get(arg))
                ).getDeptName();
                params.put("deptName", deptName);
                break;
            case "positionId":
                // 如果是positionId，查找出对应的positionName
                String positionName = positionInfoMapper.selectPositionInfoById(
                        ParamUtil.parseId((String) field.get(arg))
                ).getPositionName();
                params.put("positionName", positionName);
                break;
            case "adminRoleId":
                // 如果是adminRoleId，查找出对应的adminRoleName
                RoleCode roleCode = RoleCode.getRoleCode(
                        ParamUtil.parseId((String) field.get(arg)).intValue());
                if (roleCode == null) {
                    throw new RuntimeException("管理员角色不存在");
                }
                params.put("adminRoleName", roleCode.getName());
                break;
            case "userId":
                // 如果是userId，查找出对应的userName
                String userName = userInfoMapper.selectUserInfoById(
                        ParamUtil.parseId((String) field.get(arg))
                ).getName();
                params.put("userName", userName);
                break;
            case "companyContactAccountId":
                // 如果是companyContactAccountId
                if (field.get(arg) == null || field.get(arg).equals("")) {
                    break;
                }
                AdminAccount companyContact = adminAccountMapper.selectAdminAccountById(
                        ParamUtil.parseId((String) field.get(arg))
                );
                params.put("companyContactUsername", companyContact.getUsername());
                params.put("companyContactName", companyContact.getName());
                params.put("companyContactPhoneNum", companyContact.getPhoneNumber());
                break;
            case "deptContactAccountId":
                // 如果是deptContactAccountId
                if (field.get(arg) == null || field.get(arg).equals("")) {
                    break;
                }
                AdminAccount deptContact = adminAccountMapper.selectAdminAccountById(
                        ParamUtil.parseId((String) field.get(arg))
                );
                params.put("deptContactUsername", deptContact.getUsername());
                params.put("deptContactName", deptContact.getName());
                params.put("deptContactPhoneNum", deptContact.getPhoneNumber());
                break;
            case "mysqlBackupId":
                // 如果是 mysqlBackupId，查找出对应的backupName
                String backupName = mysqlBackupMapper.selectMysqlBackupById(
                        ParamUtil.parseId((String) field.get(arg))
                ).getBackupName();
                params.put("backupName", backupName);
                break;
        }
    }
}
